package com.cxcs.cxcsojcodesandbox;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.dfa.FoundWord;
import cn.hutool.dfa.WordTree;
import com.cxcs.cxcsojcodesandbox.model.ExcuteMessage;
import com.cxcs.cxcsojcodesandbox.model.ExecuteCodeRequest;
import com.cxcs.cxcsojcodesandbox.model.ExecuteCodeResponse;
import com.cxcs.cxcsojcodesandbox.model.JudgeInfo;
import com.cxcs.cxcsojcodesandbox.model.enums.ExectueResponseStatusEnum;
import com.cxcs.cxcsojcodesandbox.model.enums.JudgeInfoMessageEnum;
import com.cxcs.cxcsojcodesandbox.security.DefaultSecurityManager;
import com.cxcs.cxcsojcodesandbox.utils.ProcessUtils;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

/**
 * java 原生代码沙箱
 *
 * @Author: CxCS
 * @Version: 1.0
 * @Date: 2024-08
 */
@Service
public class JavaNativeCodeSandBox implements CodeSandBox {

    private static final String GLOBAL_CODE_DIR_NAME = "tmpCode"; // 全局目录
    private static final String GLOBAL_JAVA_CLASS_NAME = "Main.java"; // 文件名
    private static final long TIME_OUT = 5000L; // 最大运行时间 5s（用于超时控制）

    private static final List<String> blackList = Arrays.asList("Files", "exec", "Runtime"); // 用户代码黑名单

    private static final WordTree WORD_TREE; // 字典树（Hutool提供的字典树工具类）
    // 使用静态代码块初始化
    static {
        WORD_TREE = new WordTree();
        WORD_TREE.addWords(blackList); // 将黑名单作为限制词加入
    }

    // 安全管理器 路径和类名
    private static final String SECURITY_MANAGER_PATH = System.getProperty("user.dir") + "\\src\\main\\resources\\security" ;
    private static final String SECURITY_MANAGER_CLASS_NAME = "DefaultSecurityManager";

    @Override
    public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
//        一、保存用户代码
        List<String> inputList = executeCodeRequest.getInputList(); // 输入样例
        String code = executeCodeRequest.getCode(); // 用户输入代码
        String language = executeCodeRequest.getLanguage();

        /*  黑名单
            校验用户代码是否危险（含有禁止词）
         */
        FoundWord foundWord = WORD_TREE.matchWord(code);
        if(foundWord != null) {
            System.out.println("包含禁止词：" + foundWord);

            JudgeInfo judgeInfo = new JudgeInfo();
            judgeInfo.setMessage(JudgeInfoMessageEnum.COMPILE_ERROR.getText());
            Throwable e = new SecurityException("程序异常");
            return createErrorResponse(e, judgeInfo);
        }

        String adminDir = System.getProperty("user.dir"); // 项目目录
        String globalCodePathName = adminDir + File.separator + GLOBAL_CODE_DIR_NAME; // 保存代码的父路径
        if (FileUtil.exist(globalCodePathName)) { // 路径不存在
            FileUtil.mkdir(globalCodePathName);  // 新建
        }
        // 把用户代码隔离存放  File.separator为“\\”
        String userCodeParenPath = globalCodePathName + File.separator + UUID.randomUUID(); // 保存用户代码文件的目录 路径
        String userCodePath = userCodeParenPath + File.separator + GLOBAL_JAVA_CLASS_NAME;  // 用户代码文件 的路径
        // 将代码存入文件
        File userCodeFile = FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);

//        二、编译代码
         String compileCmd = String.format("javac -encoding utf-8 %s", userCodeFile.getAbsolutePath()); // 编译命令

//        String compileCmd = String.format("java -Dfile.encoding=UTF-8 -cp %s;%s -Djava.security.manager=DefaultSecurityManager Main\n",
//                userCodeFile.getAbsolutePath());


        // todo Java执行命令
        try {
            Process compileProcess = Runtime.getRuntime().exec(compileCmd);
            ExcuteMessage excuteMessage = ProcessUtils.getProcessMessge(compileProcess, "编译"); // 获取进程执行的信息

        } catch (IOException e) {
            // 错误处理 返回错误信息响应
            JudgeInfo judgeInfo = new JudgeInfo();
            judgeInfo.setMessage(JudgeInfoMessageEnum.COMPILE_ERROR.getText()); // 编译错误
            return createErrorResponse(e, judgeInfo);
        }


//        三、执行代码，获取结果
        List<ExcuteMessage> excuteMessageList = new ArrayList<>();
        // 对每一个输入用例 运行一次
        for(String inputArgs : inputList) {
//            String runCmd = String.format("java -Dfile.encoding=UTF-8 -cp %s Main %s", userCodeParenPath, inputArgs); // 运行命令
            System.out.println("管理器位置" + SECURITY_MANAGER_PATH);
            // 运行文件时 启动 recourses/security/DefaultSecurityManager.class 下的安全管理
            String runCmd = String.format("java -Xmx256m -Dfile.encoding=UTF-8 -cp %s;%s -Djava.security.manager=%s Main %s",
                    userCodeParenPath, SECURITY_MANAGER_PATH, SECURITY_MANAGER_CLASS_NAME, inputArgs);

            try {
                Process runProcess = Runtime.getRuntime().exec(runCmd);
                JudgeInfo judgeInfo = new JudgeInfo();

                // 创建守护线程
                new Thread(() -> {
                    try {
                        Thread.sleep(TIME_OUT); // 睡眠5s
                        // 程序还在运行
                        if (runProcess.isAlive()) {
                            runProcess.destroy(); // 销毁
                            System.out.println("进程运行超时！");
                            // 返回超时信息
                            judgeInfo.setMessage(JudgeInfoMessageEnum.TIME_LIMIT_EXCEEDED.getText());
                        }

                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }).start();

                // 判题信息不为空，进程被强行终止 超时
                if (judgeInfo.getMessage() != null) {
                    Throwable e = new SecurityException("运行超时");
                    return createErrorResponse(e, judgeInfo);
                }

                ExcuteMessage excuteMessage = ProcessUtils.runInteractProcess(runProcess, inputArgs, "运行"); // 获取进程执行的信息
                System.out.println(excuteMessage);

                excuteMessageList.add(excuteMessage);
            } catch (IOException e) {
                // 错误处理 返回错误信息响应
                JudgeInfo judgeInfo = new JudgeInfo();
                judgeInfo.setMessage(JudgeInfoMessageEnum.RUNTIME_ERROR.getText());
                return createErrorResponse(e, judgeInfo);
            }
        }

//        四、整理结果
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        List<String> outputList = new ArrayList<>(); // 执行的输出结果
        long maxTime = 0; // 所有用例运行时长的最大值
        JudgeInfo judgeInfo = new JudgeInfo();

        for (ExcuteMessage excuteMessage : excuteMessageList) {
            if (StrUtil.isNotBlank(excuteMessage.getErrorMessage())) {
                // 存在错误信息 退出
                executeCodeResponse.setMessage(excuteMessage.getErrorMessage());
                executeCodeResponse.setStatus(ExectueResponseStatusEnum.FAILED.getValue());
                judgeInfo.setMessage(JudgeInfoMessageEnum.RUNTIME_ERROR.getText()); // 运行时异常
                break;
            }

            if(excuteMessage.getRunTime() != null) {
                maxTime = Math.max(maxTime, excuteMessage.getRunTime());
            }
            // 输出结果
            outputList.add(excuteMessage.getMessage());
        }
        // 所有用例 正常执行
        if (outputList.size() == excuteMessageList.size()) {
            executeCodeResponse.setStatus(ExectueResponseStatusEnum.SUCCEED.getValue());
            executeCodeResponse.setOutputList(outputList);
        }
        judgeInfo.setTime(maxTime);
        // 获取程序执行运行内存 暂不实现
        executeCodeResponse.setJudgeInfo(judgeInfo);

//        五、文件清理
        if (userCodeFile.getParentFile() != null) {
            boolean del = FileUtil.del(userCodePath);
            System.out.println("删除" + (del ? "成功" : "失败"));
        }

//        六、错误处理

        System.out.println(executeCodeResponse);
        return executeCodeResponse;
    }

    /**
     *  错误处理：对于抛出异常的地方
     *      快速生成 执行错误响应
     * @param e 异常信息
     * @return
     */
    private ExecuteCodeResponse createErrorResponse(Throwable e, JudgeInfo judgeInfo) {
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        executeCodeResponse.setOutputList(new ArrayList<>());
        executeCodeResponse.setMessage(e.getMessage());
        // 代码沙箱 执行报错
        executeCodeResponse.setStatus(ExectueResponseStatusEnum.FAILED.getValue()); // 运行失败
        executeCodeResponse.setJudgeInfo(judgeInfo);

        return executeCodeResponse;
    }

    // 测试
    public static void main(String[] args) {
        /*
            手动启动 Security 安全管理器
            不可在该处启用（主程序），会限制整个项目，须在Process进程出启用
         */
//        System.setSecurityManager(new DefaultSecurityManager());

        String code = ResourceUtil.readStr("testCode/simpleCode/Main.java", StandardCharsets.UTF_8); // 获取资源目录下指定目录的 文件内容

        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
        executeCodeRequest.setInputList(Arrays.asList("1 2", "99 100", "66 55"));
        executeCodeRequest.setCode(code);
        executeCodeRequest.setLanguage("java");

        CodeSandBox codeSandBox = new JavaNativeCodeSandBox();
        codeSandBox.executeCode(executeCodeRequest);
    }
}
